﻿/*
 * OARepository, Version=1.0.0.0, Culture=neutral, PublicKeyToken=25cd6f6b3bee9bbc
 * 
 */

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Web;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Net.Mime;
using System.Net;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;

namespace OARepository
{
    public class Sword : IHttpHandler
    {
        private RepoContext repoContext = null;
        private static XNamespace swordNS = "http://purl.org/net/sword/terms/";

        protected RepoContext RepoContext
        {
            get
            {
                return repoContext;
            }
            set
            {
                repoContext = value;
            }
        }

        /// <summary>
        /// You will need to configure this handler in the web.config file of your 
        /// web and register it with IIS before being able to use it. For more information
        /// see the following link: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpHandler Members

        public bool IsReusable
        {
            // Return false in case your Managed Handler cannot be reused for another request.
            // Usually this would be false in case you have some state information preserved per request.
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            string method = context.Request.HttpMethod.ToLower();
            string action = null;

            List<string> path = new List<string>(context.Request.PathInfo.Split('/'));
            if (path.Count > 0)
                path.RemoveAt(0); // strip leading '/'

            /*response.Write(Assembly.GetExecutingAssembly().FullName + "\n\n");
            response.Write(context.Request.HttpMethod + "\n\n");
            response.Write(web.Title + "\n\n");*/

            if (path.Count > 2)
                action = path[2];

            if (path.Count > 1)
                RepoContext = new RepoContext(path[0], path[1]);
            else if (path.Count > 0)
                RepoContext = new RepoContext(path[0]);
            else
                RepoContext = new RepoContext();

            switch (method)
            {
                case "get":
                case "head":
                    if (action != null && action == "content")
                        this.RenderContent(context);
                    else if (RepoContext.Item != null)
                        this.RenderItem(context);
                    else if (RepoContext.List != null)
                        this.RenderList(context);
                    else
                        this.RenderMenu(context);
                    break;
                case "put":
                    if (RepoContext.Item != null && action != null && action == "content")
                    {
                        this.DeleteItemContents(context);
                        this.CreateItemContent(context);
                    }
                    else
                        this.RenderErr(context, HttpStatusCode.MethodNotAllowed);
                    break;
                case "post":
                    if (RepoContext.Item != null && action != null && action == "content")
                        this.CreateItemContent(context);
                    else if (RepoContext.Item == null && RepoContext.List != null)
                        this.CreateItem(context);
                    else
                        this.RenderErr(context, HttpStatusCode.MethodNotAllowed);
                    break;
                case "delete":
                    if (RepoContext.Item != null && action == "content")
                        this.DeleteItemContents(context);
                    else if (RepoContext.Item != null && action == null)
                        this.DeleteItem(context);
                    else if (RepoContext.Item != null && action == "content")
                        this.DeleteItemContents(context);
                    else
                        this.RenderErr(context, HttpStatusCode.MethodNotAllowed);
                    break;
                default:
                    this.RenderErr(context, HttpStatusCode.MethodNotAllowed);
                    break;
            }
        }

        #endregion

        protected void ExtendNamespace(Dictionary<XmlQualifiedName, string> attributes)
        {
            attributes.Add(
                new XmlQualifiedName("sword", "http://www.w3.org/2000/xmlns/"),
                "http://purl.org/net/sword/terms/"
                );
            attributes.Add(
                new XmlQualifiedName("dcterms", "http://www.w3.org/2000/xmlns/"),
                "http://purl.org/dc/terms/"
                );
        }

        protected string GetPreferredContentType(List<string> types)
        {
            foreach (string accept in HttpContext.Current.Request.AcceptTypes)
            {
                List<string> parts = new List<string>(accept.Split(';'));
                foreach (string type in types)
                    if (parts[0].Trim() == type)
                        return type;
            }

            return null;
        }

        /// <summary>
        /// Get the filename provided by the uploading user.
        /// </summary>
        /// <returns>A string or null if not given.</returns>
        protected string GetPreferredFilename()
        {
            HttpContext context = HttpContext.Current;

            ContentDisposition disposition = new ContentDisposition();
            if (context.Request.Headers["Content-Disposition"] != null)
            {
                disposition = new ContentDisposition(context.Request.Headers["Content-Disposition"]);
            }
            string filename = disposition.FileName;
            if (filename != null && filename.Length == 0)
                filename = null;

            return filename;
        }
        protected string GetPreferredFilename(string filename)
        {
            string fn = GetPreferredFilename();
            return fn != null ? fn : filename;
        }

        protected SyndicationItem Syndicate(SPListItem item)
        {
            SyndicationItem entry = RepoContext.Syndicate(item);
            HttpContext context = HttpContext.Current;
            SPWeb web = SPContext.Current.Web;

            string baseUrl = web.Url + context.Request.FilePath;

            Uri editUri = new Uri(baseUrl + "/" + RepoContext.List.ID.ToString() + "/" + item.UniqueId.ToString());
            entry.Links.Add(new SyndicationLink(editUri, "edit", null, null, 0));

            Uri editMediaUri = new Uri(editUri + "/content");
            entry.Links.Add(new SyndicationLink(editMediaUri, "edit-media", null, null, 0));

            entry.Links.Add(new SyndicationLink(new Uri(RepoContext.ItemUrl(item)), "alternate", null, "text/html", 0));

            return entry;
        }

        private void RenderErr(HttpContext context, HttpStatusCode code)
        {
            this.RenderErr(context, (int)code);
        }
        private void RenderErr(HttpContext context, int code)
        {
            context.Response.Clear();
            context.Response.StatusCode = code;
            context.Response.End();
        }

        protected void RenderMenu(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            context.Response.ContentType = "application/atomserv+xml";

            ServiceDocument document = new ServiceDocument();
            ExtendNamespace(document.AttributeExtensions);

            document.ElementExtensions.Add(
                new XElement(swordNS + "version",
                    new XText("2.0")
                ));
            
            Workspace workspace = new Workspace();
            workspace.Title = new TextSyndicationContent(SPContext.Current.Site.ToString());
            document.Workspaces.Add(workspace);

            foreach (SPList list in RepoContext.GetLists().Values)
            {
                ResourceCollectionInfo coll = new ResourceCollectionInfo(
                           list.Title,
                           new Uri(context.Request.Url.AbsoluteUri + "/" + list.ID)
                        );
                coll.Accepts.Add("*/*");
                coll.ElementExtensions.Add(
                    new XElement(swordNS + "mediation",
                            new XText("false")
                        )
                    );
                workspace.Collections.Add(coll);
            }

            XmlWriterSettings xsettings = new XmlWriterSettings();
            xsettings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(context.Response.Output, xsettings))
            {
                document.Save(writer);
            }
        }

        protected void RenderList(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;
            HttpResponse response = context.Response;
            
            context.Response.AddHeader("X-Folder", RepoContext.List.RootFolder.ToString());

            context.Response.ContentType = "application/atom+xml; type=feed";

            SyndicationFeed feed = new SyndicationFeed(RepoContext.List.Title, "", context.Request.Url);
            ExtendNamespace(feed.AttributeExtensions);

            Collection<SyndicationItem> items = new Collection<SyndicationItem>();
            foreach (SPListItem item in RepoContext.List.Items)
            {
                items.Add(Syndicate(item));
            }
            feed.Items = items;

            XmlWriterSettings xsettings = new XmlWriterSettings();
            xsettings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(context.Response.Output, xsettings))
            {
                feed.SaveAsAtom10(writer);
            }
        }

        private void RenderItem(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            List<string> available = new List<string>();
            available.Add("application/atom+xml");
            string content_type = GetPreferredContentType(available);

            switch (content_type)
            {
                case "application/atom+xml":
                    this.RenderItemAtom(context);
                    break;
                default:
                    SPUtility.Redirect(web.Url + "/" + RepoContext.Item.Url, SPRedirectFlags.Default, context);
                    break;
            }
        }
        private void RenderItemAtom(HttpContext context)
        {
            context.Response.ContentType = "application/atom+xml; type=entry";

            SyndicationItem entry = Syndicate(RepoContext.Item);
            ExtendNamespace(entry.AttributeExtensions);

            XmlWriterSettings xsettings = new XmlWriterSettings();
            xsettings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(context.Response.Output, xsettings))
            {
                entry.SaveAsAtom10(writer);
            }
        }

        /// <summary>
        /// Create a new item in the current list from the content submitted
        /// with the request.
        /// </summary>
        /// <param name="context"></param>
        private void CreateItem(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            web.AllowUnsafeUpdates = true; // Sharepoint thinks we're in a 'GET'
            SPUtility.EnsureAuthentication(web);

            string content_type = context.Request.ContentType;
            if (content_type == null || content_type.Length == 0)
                content_type = "application/octet-stream";
            content_type = (content_type.Split(';'))[0].Trim();

            SPListItem item;
            
            switch (content_type)
            {
                case "application/atom+xml":
                    item = CreateAtomItem(context);
                    break;
                default:
                    item = CreateBinaryItem(context);
                    break;
            }

            RepoContext.Item = item;
            RenderItemAtom(context);

            context.Response.StatusCode = (int)HttpStatusCode.Created;
            context.Response.Headers.Add("Location", context.Request.Url.AbsoluteUri + "/" + item.UniqueId.ToString());
        }
        /// <summary>
        /// Create a new SPListItem from application/atom+xml content.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private SPListItem CreateAtomItem(HttpContext context)
        {
            Atom10ItemFormatter parser = new Atom10ItemFormatter();
            using(XmlReader reader = XmlReader.Create(context.Request.InputStream))
            {
                parser.ReadFrom(reader);
            }
            SyndicationItem entry = parser.Item;
            SPListItem item = RepoContext.List.Items.Add();
            item["Title"] = entry.Title.Text;
            item.Update();
            return item;
        }
        /// <summary>
        /// Create a new SPListItem with the content attached as a new file.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private SPListItem CreateBinaryItem(HttpContext context)
        {
            string filename = GetPreferredFilename("main.bin");

            SPListItem item = null;
            if (RepoContext.List.BaseType == SPBaseType.DocumentLibrary)
            {
                SPFolder folder = RepoContext.List.RootFolder;
                SPFile file = folder.Files.Add(filename, new byte[] { 0x00 });
                file.SaveBinary(context.Request.InputStream);
                file.Update();

                item = file.Item;
                item["Title"] = filename;
                item.Update();
            }
            else
            {
                item = RepoContext.List.Items.Add();
                item["Title"] = filename;
                item.Attachments.Add(filename, new byte[] { 0x00 });
                item.Update();

                SPFile file = SPContext.Current.Web.GetFile(item.Attachments.UrlPrefix + filename);
                file.SaveBinary(context.Request.InputStream);
                file.Update();
            }

            return item;
        }

        private void DeleteItem(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            web.AllowUnsafeUpdates = true; // Sharepoint thinks we're in a 'GET'
            SPUtility.EnsureAuthentication(web);

            RepoContext.Item.Delete();

            context.Response.StatusCode = (int)HttpStatusCode.NoContent;
            context.Response.End();
        }

        private void RenderContent(HttpContext context)
        {
            List<string> available = new List<string>();
            available.Add("application/atom+xml");
            string content_type = GetPreferredContentType(available);

            List<string> files = RepoContext.GetFiles();
            if (files.Count == 0)
            {
                RenderErr(context, HttpStatusCode.NotFound);
                return;
            }

            switch (content_type)
            {
                case "application/atom+xml":
                    break;
                default:
                    SPUtility.Redirect(files[0], SPRedirectFlags.Default, context);
                    break;
            }
        }

        /// <summary>
        /// Attaches a new file to the list item.
        /// </summary>
        /// <param name="context"></param>
        private void CreateItemContent(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            web.AllowUnsafeUpdates = true; // Sharepoint thinks we're in a 'GET'
            SPUtility.EnsureAuthentication(web);

            string filename = GetPreferredFilename("main.bin");
            SPListItem item = RepoContext.Item;

            try
            {
                item.Attachments.Add(filename, new byte[] { 0x00 });
                item.Update();
            }
            catch (SPException)
            {
                // replace existing file
            }

            SPFile file = SPContext.Current.Web.GetFile(item.Attachments.UrlPrefix + filename);
            file.SaveBinary(context.Request.InputStream);
            file.Update();

            context.Response.StatusCode = (int)HttpStatusCode.Created;
            context.Response.Headers.Add("Location", item.Attachments.UrlPrefix + filename);
        }

        private void DeleteItemContents(HttpContext context)
        {
            SPWeb web = SPContext.Current.Web;

            web.AllowUnsafeUpdates = true; // Sharepoint thinks we're in a 'GET'
            SPUtility.EnsureAuthentication(web);

            foreach (string url in RepoContext.GetFiles())
            {
                SPFile file = SPContext.Current.Web.GetFile(url);
                file.Recycle();
            }

            context.Response.StatusCode = (int)HttpStatusCode.NoContent;
        }
    }
}
